'''
Created on 04/09/2009

@author: Edward Huynh, Tony Allan
'''
from runtime.task import Task
from datastore import *
from runtime.spaces import *
from Queue import Queue
import unittest
import time

LOGGING = logging.WARNING  # logging.DEBUG
DATABASE_NAME = 'node.db3'

class TestingTask(Task):
    def cb_start(self, method, uri, new_uri, result, spaces_context, **kwargs):
        '''
        Note about the return from a process callback:
            Within a spaces context, for a process handler or subsequent callback, someone must
            set do_return to True. If the callback returns a value, then this is past as the second
            element of the Tuple.
        '''
        uri_xxx = spaces_context.uri('http://xxx/x')
        watch_ref = spaces_context.watch(['put'], uri_xxx, 'cb_test')
        #return (False, None)

    def cb_test(self, method, uri, new_uri, result, spaces_context, **kwargs):
        res1 = spaces_context.get(new_uri)
        res2 = Resource()
        res2.ghi = 'qwerty'
        uri_yyy = spaces_context.uri('http://yyy/x')
        spaces_context.put(uri_yyy, res2)
        #return (False, None)        
        
class DrivingTask(Task):
    def cb_start(self, method, uri, new_uri, result, spaces_context, **kwargs):
        uri_xxx = spaces_context.uri('http://xxx/x')
        res1 = Resource()
        res1.abc = 'qwerty'
        spaces_context.put(uri_xxx, res1)
        #return (False, None)

class TaskTest(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_init(self):
        '''[Task] test init runs successfully'''
        ds = DataStore(DATABASE_NAME)
        ns_dict = {'a': 'http://www.example.com/',
                   'tasks': 'http://task.com/'}
        spaces = Spaces()
        spaces.ns = ns_dict
        uri_tasks = spaces.uri('tasks:')
        # dummy task def resource
        task_def_resource = Resource()
        task = Task(ds, spaces, uri_tasks.a, task_def_resource)
        node = spaces.uri_tree.find_node(uri_tasks.a)
        # check that the watch is there
        self.assertTrue(('cb_update', task.spaces_context, {}) in node.watch.values())
        self.assertTrue(('cb_stop', task.spaces_context, {}) in node.watch.values())

    def test_run_and_stop(self):
        '''[Task] test run and stop'''
        ds = DataStore(DATABASE_NAME)
        ns_dict = {'a': 'http://www.example.com/',
                   'tasks': 'http://task.com/'}
        spaces = Spaces()
        spaces.ns = ns_dict
        uri_tasks = spaces.uri('tasks:')
        # dummy task def resource
        task_def_resource = Resource()        
        # dummy resource for callbacks
        res1 = Resource()
        # status resource to stop the task
        res2 = Resource()
        # dummy uri for the first callback
        uri_1 = spaces.uri(None)
        task = Task(ds, spaces, uri_tasks.a, task_def_resource)
        # create dummy method to call and bind to task
        def blah(method, uri, res, result, context):
            res.blah = True
        def yada(method, uri, res, result, context):
            res.yada = True
        task.blah = blah
        task.yada = yada
        # put callback (string) on task queue to be processed
        task.spaces_context.queue.put(('blah', 'put', uri_1, res1, None, task.spaces_context, {}))
        # put callback (method) on task queue to be processed
        task.spaces_context.queue.put((task.yada, 'put', uri_1, res1, None, task.spaces_context, {}))
        # this callback is to stop the loop
        task.spaces_context.queue.put(('cb_stop_test', 'put', uri_tasks.a, res2, None, task.spaces_context, {}))
        task.run()              # thread NOT started
        self.assertTrue(res1.blah)
        self.assertTrue(res1.yada)

#    def gp_watch(self, method, uri, res, result):
#        print('[gp_watch] %s' % str(res))
             
    def test_threads(self):
        '''[Task] test with threads'''
        print('')
        ds = DataStore(DATABASE_NAME)
        ns_dict = {'a': 'http://www.example.com/'}
        spaces = Spaces()
        queue = Queue()
        spaces_context = SpacesContext(spaces, queue)
        #spaces.ns = ns_dict
        spaces_context.load_bindings(ns_dict)
        # dummy task def resource
        task_def_resource = Resource()         
        #
        gp_uri = spaces_context.uri('a:').abc
        gp_res = Resource()
        gp_res.a = 123
        spaces_context.put(gp_uri, gp_res)
        gp_res_updated = spaces_context.get(gp_uri)
        self.assertEqual(gp_res, gp_res_updated)
        #
        # start the two tasks
        taskt_uri = spaces_context.uri('a:').taskt
        taskt = TestingTask(ds, spaces, taskt_uri, task_def_resource)
        taskt.task_start()
        #
        time.sleep(0)
        taskd_uri = spaces_context.uri('a:').taskd
        taskd = DrivingTask(ds, spaces, taskd_uri, task_def_resource)
        taskd.task_start()
        #
        time.sleep(1)
        uri_xxx = spaces_context.uri('http://xxx/x')
        res1 = spaces_context.get(uri_xxx)
        uri_yyy = spaces_context.uri('http://yyy/x')
        res2 = spaces_context.get(uri_yyy)
        self.assertEqual(res1.abc, res2.ghi)
        #
        taskt.task_stop()
        taskd.task_stop()
        time.sleep(1)

    def test_deferred_return(self):
        '''[Task] test deferred return'''
        ds = DataStore(DATABASE_NAME)
        ns_dict = {'tasks': 'http://task.com/',
                   'a': 'http://a.com/',
                   'b': 'http://b.com/',
                   'c': 'http://c.com/',
                   'd': 'http://d.com/',
                   'e': 'http://e.com/'}
        spaces = Spaces()
        spaces.ns = ns_dict
        # dummy resource for first callback
        res1 = Resource()
        # URI's for tasks
        uri_a = spaces.uri('a:')
        uri_b = spaces.uri('b:')
        uri_c = spaces.uri('c:')
        uri_d = spaces.uri('d:')
        uri_e = spaces.uri('e:')
        uri_tasks = spaces.uri('tasks:')
        # dummy task def resource
        task_def_resource = Resource()
        # lock is used to indicate when all the calls between tasks are complete
        # notice in task_1 callback method that it releases the lock
        # as this is the last call in the chain of calls between the tasks
        lock = threading.Lock()
        lock.acquire()
        
        # the test is designed so that
        # task_1 calls task_2, task_2 calls task_3 and task_4, and task_4 calls task_5. In terms of method calls the flow is shown below:
        # method_1 -> method_2 -> method_3 -> method_2_callback_1 -> method_4 -> method_5 -> method_4_callback -> method_2_callback_2 -> method_1_callback
        # task_1 calls a get on task_2 API and waits for a callback
        task_1 = Task(ds, spaces, uri_tasks.a, task_def_resource)     
        def task_1_start(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.process(uri_a.x, get='method_1')  
        def method_1(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.get(uri_b.x, 'method_1_callback')
        def method_1_callback(method, uri, res, result, spaces_context, **kwargs):
            res1.called = True
            res1.text = res.text
            lock.release()
            return (False, None)
        task_1.cb_start = task_1_start
        task_1.method_1 = method_1
        task_1.method_1_callback = method_1_callback
        
        # task_2 calls a get on task_3 API and waits for a callback
        task_2 = Task(ds, spaces, uri_tasks.b, task_def_resource) 
        def task_2_start(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.process(uri_b.x, get='method_2')    
        def method_2(method, uri, res, result, spaces_context):
            spaces_context.wow = 'blah'
            spaces_context.get(uri_c.x, 'method_2_callback_1')
        def method_2_callback_1(method, uri, res, result, spaces_context, **kwargs):
            res.text += 'Task2Callback1->'
            spaces_context.get(uri_d.x, 'method_2_callback_2')
            spaces_context.savedres = res
        def method_2_callback_2(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.wow
            savedres = spaces_context.savedres
            savedres.text += res.text + 'Task2Callback2->'
            return (True, savedres)        
        task_2.cb_start = task_2_start
        task_2.method_2 = method_2
        task_2.method_2_callback_1 = method_2_callback_1
        task_2.method_2_callback_2 = method_2_callback_2
        
        # task_3 processes and returns
        task_3 = Task(ds, spaces, uri_tasks.c, task_def_resource) 
        def task_3_start(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.process(uri_c.x, get='method_3')    
            return (False, None)         
        def method_3(method, uri, res, result, spaces_context, **kwargs):
            res = Resource()
            res.text = 'Hello Ed.' + 'Task3->'
            return (True, res)
        task_3.cb_start = task_3_start
        task_3.method_3 = method_3

        # task_2 calls a get on task_3 API and waits for a callback
        task_4 = Task(ds, spaces, uri_tasks.d, task_def_resource) 
        def task_4_start(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.process(uri_d.x, get='method_4')    
            return (False, None)    
        def method_4(method, uri, res, result, spaces_context, **kwargs):
            res = spaces_context.get(uri_e.x)
            res.text += 'Task4->'
            return (True, res)
        task_4.cb_start = task_4_start
        task_4.method_4 = method_4
        
        # task_5 processes and returns
        task_5 = Task(ds, spaces, uri_tasks.e, task_def_resource) 
        def task_5_start(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.process(uri_e.x, get='method_5')    
            return (False, None)         
        def method_5(method, uri, res, result, spaces_context, **kwargs):
            res = Resource()
            res.text = 'Task5->'
            return (True, res)
        task_5.cb_start = task_5_start
        task_5.method_5 = method_5        
              
        task_1.start()
        task_2.start()
        task_3.start()
        task_4.start()
        task_5.start()
        # sleep a little to allow all the tasks to start up and get ready
        time.sleep(1)
        # instigate the first callback
        uri = spaces.uri('start:')
        task_1.spaces_context.queue.put(('method_1', 'get', uri, None, None, None, {}))
        
        lock.acquire()  # now lock until the method_1_callback releases
        lock.release()  # release the lock again
        
        # stop all the tasks
        task_1.task_stop()
        task_2.task_stop()
        task_3.task_stop()
        task_4.task_stop()
        task_5.task_stop()        
        
        # check the method_1_callback was called
        self.assertTrue(res1.called)
        self.assertEqual(res1.text, 'Hello Ed.Task3->Task2Callback1->Task5->Task4->Task2Callback2->')    
        
    def test_exception_handling(self):
        '''[Task] test exception handling when task executes callback'''
        ds = DataStore(DATABASE_NAME)
        ns_dict = {'tasks': 'http://task.com/',
                   'a': 'http://a.com/',
                   'b': 'http://b.com/'
                   }
        spaces = Spaces()
        spaces.ns = ns_dict
        # dummy resource for first callback
        res1 = Resource()
        # URI's for tasks
        uri_b = spaces.uri('b:')
        uri_tasks = spaces.uri('tasks:')
        # dummy task def resource
        task_def_resource = Resource()
        # lock is used to indicate when all the calls between tasks are complete
        # notice in task_1 callback method that it releases the lock
        # as this is the last call in the chain of calls between the tasks
        lock = threading.Lock()
        lock.acquire()
        
        # the test is designed so that
        # task_2 raises an exception which is passed back to task_1. In terms of method calls the flow is shown below:
        # method_1 -> method_2 -> method_1_callback
        # task_1 calls a get on task_2 API and waits for a callback
        task_1 = Task(ds, spaces, uri_tasks.a, task_def_resource)
        def task_1_start(method, uri, res, result, spaces_context, **kwargs):
            pass
        def method_1(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.get(uri_b.x, 'method_1_callback')
        def method_1_callback(method, uri, res, result, spaces_context, **kwargs):
            res1.called = True
            res1.exception = result
            lock.release()
            return (False, None)
        task_1.cb_start = task_1_start
        task_1.method_1 = method_1
        task_1.method_1_callback = method_1_callback
        
        # task_2 calls a get on task_3 API and waits for a callback
        task_2 = Task(ds, spaces, uri_tasks.b, task_def_resource) 
        def task_2_start(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.process(uri_b.x, get='method_2')    
        def method_2(method, uri, res, result, spaces_context, **kwargs):
            spaces_context.wow = 'blah'
            raise Exception() 
        task_2.cb_start = task_2_start
        task_2.method_2 = method_2    
              
        task_1.start()
        task_2.start()
        # sleep a little to allow all the tasks to start up and get ready
        time.sleep(1)
        # instigate the first callback
        uri = spaces.uri('start:')
        task_1.spaces_context.queue.put(('method_1', 'get', uri, None, None, None, {}))
        
        lock.acquire()  # now lock until the method_1_callback releases
        lock.release()  # release the lock again
        
        # stop all the tasks
        task_1.task_stop()
        task_2.task_stop()        
        
        # check the method_1_callback was called
        self.assertTrue(res1.called)
        # check we captured the exception
        self.assertTrue(type(res1.exception) is type(Exception()))
            
if __name__ == "__main__":
    unittest.main()